﻿using System.Collections.Generic;
using System.Xml;
using System.IO;
using System.Data;
using SdmxMl.Common;
using SdmxMl.Structure;
using SdmxMl.Helper;
using SdmxMl.GenericMetadata;
using System.Windows.Forms;
using SdmxMl.Multiplexor;

namespace SdmxMl.Manager
{
    /// <summary> Class container for an SDMX-ML artefact type </summary>
    public class ArtefactManager : ISdmxNewChild, ISdmxDelChild
    {
        #region Internal Members

        protected SdmxArtefactType artefactType;
        protected ArtefactList artefactList;

        #endregion

        #region Properties

        public SdmxArtefactType ArtefactType
        {
            get { return artefactType; }
        }

        public ArtefactList ArtefactList
        {
            get { return artefactList; }
        }

        #endregion

        #region C'tor

        public ArtefactManager(SdmxArtefactType aType)
        {
            artefactType = aType;
            artefactList = new ArtefactList();
        }
        #endregion

        #region Public operazions on container

        // Clear container content
        public virtual void Clear()
        {
            artefactList.Clear();
        }

        public virtual int Count
        {
            get { return artefactList.Count; }
        }

        public virtual bool Exists(Artefact art)
        {
            bool exist = false;

            if (art != null)
            {
                foreach (Artefact a in artefactList)
                {
                    if (a.Id == art.Id && a.AgencyId == art.AgencyId && a.Version == art.Version)
                    {
                        exist = true;
                        break;
                    }
                }
            }

            return exist;
       }


        /// <summary> Fetch artefact from container </summary>
        /// <param name="fullId">Full identification of artefact to fetch</param>
        /// <returns>Matching artefact or null</returns>
        public virtual Artefact GetFromFullId(string fullId)
        {
            Artefact artefact = null;
            foreach (Artefact a in artefactList)
            {
                if (a.FullIdent == fullId)
                {
                    artefact = a;
                    break;
                }
            }

            return artefact;
        }
        #endregion

        #region Load from XML, Artefacts of container type

        /// <summary> SDMX 2.0 Artefact type loading</summary>
        /// <param name="xDoc">The document</param>
        /// <param name="nsmgr">associated namespace manager</param>
        /// <returns>true if any artefact loaded</returns>
        private bool LoadFrom2_0(XmlDocument xDoc, SdmxNamespaceManager nsmgr)
        {
            bool found = false;
            string query = string.Format("//S:{0}", artefactType.ToString());

            List<XmlNode> nodeList = Xmlhelper.SelectNodesElement(xDoc, query, nsmgr);
            if (nodeList.Count > 0)
            {
                found = true;

                // Special case Concepts (global in SDMX 1.1 & Under Concepr Scheme from 2.0
                if (artefactType == SdmxArtefactType.Concepts)
                {
                    // Has global concepts ?
                    query = string.Format("./structure:Concept");
                    nodeList = Xmlhelper.SelectNodesElement(nodeList[0], query, nsmgr);
                    if (nodeList.Count > 0)
                    {
                        //TODO if global concepts comes from more than one agency and or more than one version,
                        //creates many global concepts scheme
                        Artefact a = CreateArtefact();
                        ConceptSchemeType cst = a as ConceptSchemeType;
                        if (cst != null)
                        {
                            cst.FillFromGlobalConcepts(nodeList, nsmgr);
                        }

                        if (a != null && artefactList.Exist(a) == false)
                            artefactList.Add(a);
                    }

                    nodeList = Xmlhelper.SelectNodesElement(xDoc,
                        string.Format("//S:{0}/structure:ConceptScheme", artefactType.ToString()), nsmgr);
                    foreach (XmlNode nd in nodeList)
                    {
                        if (nd.NodeType == XmlNodeType.Element)
                        {
                            Artefact a = CreateArtefact(nd, nsmgr);
                            if (a != null && artefactList.Exist(a) == false)
                            {
                                a = CompleteSmsExternalArtefact(this.ArtefactType, a);
                                artefactList.Add(a);
                            }
                        }
                    }

                }
                else
                {
                    foreach (XmlNode nd in nodeList[0].ChildNodes)
                    {
                        if (nd.NodeType == XmlNodeType.Element)
                        {
                            Artefact a = CreateArtefact(nd, nsmgr);
                            if (a != null && artefactList.Exist(a) == false)
                            {
                                a = CompleteSmsExternalArtefact(this.ArtefactType, a);
                                artefactList.Add(a);
                            }
                        }
                    }
                }
            }

            return found;
        }

        /// <summary>Load in container all artefacts of the container type</summary>
        /// <param name="xDoc">SDMX-ML document containing artefacts as XML</param>
        /// <param name="nsmgr">Namespace manager helper</param>
        /// <returns>True if some artefact of container type found in document. </returns>
        public virtual bool LoadFromDocument(XmlDocument xDoc, SdmxNamespaceManager nsmgr)
        {
            bool found = false;
            // Treat 2.0
            if (nsmgr.VersionSdmx == SdmxVersion.SDMX_20)
                return LoadFrom2_0(xDoc, nsmgr);

            // Treat 2.1
            string query = string.Format("//S:Structures/structure:{0}",
                    nsmgr.GetArtefactGroupTag(artefactType));

            List<XmlNode> nodeList = Xmlhelper.SelectNodesElement(xDoc, query, nsmgr);
            if (nodeList.Count > 0)
            {
                found = true;

                // Special case Organization Schemes holds differents Artefacts types
                if (artefactType == SdmxArtefactType.OrganisationSchemes)
                {
                    // TODO
                }
                else
                {
                    // Foreach Artefact
                    foreach (XmlNode nd in nodeList[0].ChildNodes)
                    {
                        if (nd.NodeType == XmlNodeType.Element)
                        {
                            Artefact a = CreateArtefact(nd, nsmgr);
                            if (a != null && artefactList.Exist(a) == false)
                            {
                                a = CompleteSmsExternalArtefact(this.ArtefactType, a);
                                artefactList.Add(a);
                            }
                        }
                    }
                }
            }
           
            return found;
        }
        #endregion

        #region Helper for fetching child ref of the artefact in TinyRegistry

        private Artefact CompleteSmsExternalArtefact(SdmxArtefactType arType, Artefact a)
        {
            // No More, was used for aggregated artefacts. To be studied if reintroduce or not

            /*
            if (a.IsExternalReference && string.IsNullOrEmpty(a.Uri))
            {
                // TRy to fetch completed one from SMS Registry
                try
                {
                    SmManager sm = new SmManager();
                    // Try to obtain from Registry

                    if (Mux.Instance.RunningOK && Mux.Instance.ExistArtefact(arType.ToString(), a.FullIdent))
                    {
                        XmlDocument xDoc = Mux.Instance.GetArtefact(arType.ToString(), a.FullIdent);
                        sm.LoadSdmxDocument(null, true, xDoc);
                        SdmxMl.Common.ArtefactRefTyped art = new SdmxMl.Common.ArtefactRefTyped(arType, a.FullIdent);
                        Artefact afull = sm.GetArtefact(art);
                        if (afull != null)
                            a = afull;
                    }
                }
                catch
                {
                }
            }
            */
            return a;
        }
        #endregion

        #region temporary File persistence of Artefact

        public string SaveTempArtefact(Artefact ar)
        {
            string path = System.IO.Path.ChangeExtension(System.IO.Path.GetTempFileName(), ".xml");
            return SaveTempArtefact(ar, path);
        }

        public string SaveTempArtefact(Artefact ar, string path)
        {
            XmlTextWriter xw = null;

            try
            {
                using (xw = new XmlTextWriter(path, System.Text.Encoding.UTF8))
                {
                    xw.Formatting = Formatting.Indented;
                    xw.WriteStartDocument();
                    if (this.ArtefactType == SdmxArtefactType.MetadataSet)
                        SdmxMl.Msg.HeaderGenerator.GetHeader().WriteXml(xw, SdmxMl.Msg.HeaderType.ReferenceScheme.MetadataSet);
                    else
                        SdmxMl.Msg.HeaderGenerator.GetHeader().WriteXml(xw, SdmxMl.Msg.HeaderType.ReferenceScheme.Structure);
                    WriteXml(xw, ar);

                    xw.WriteEndDocument();
                    xw.Flush();
                }
            }
            catch (System.Exception ex)
            {
                string s = ex.Message;
                path = string.Empty;
                if (xw != null)
                    xw.Close();
            }

            return path;
        }

        #endregion

        #region XML Document representation of Artefact

        /// <summary>Presents artefact object as an SDMX-ML XML document</summary>
        /// <param name="ia">the artefact object</param>
        /// <returns>the XML document representation of the artefact</returns>
        public virtual XmlDocument GetAsDocument(ISdmxArtefactRef  ia)
        {
            XmlDocument xDoc = new XmlDocument();
            Artefact ar = ia as Artefact;
            if (ar != null)
            {
                using (MemoryStream m = new MemoryStream(1024))
                {
                    using (XmlTextWriter xw = new XmlTextWriter(m, System.Text.Encoding.UTF8))
                    {
                        xw.Formatting = Formatting.Indented;
                        xw.WriteStartDocument();

                        SdmxMl.Msg.HeaderGenerator.GetHeader().WriteXml(xw, SdmxMl.Msg.HeaderType.ReferenceScheme.Structure);
                        WriteXml(xw, ar);

                        xw.WriteEndDocument();
                        xw.Flush();

                        m.Flush();
                        m.Seek(0, SeekOrigin.Begin);
                        xDoc.Load(m);
                    }
                }
            }

            return xDoc;
        }
        #endregion

        #region New Version Creation

        public void CreateNewVersion(Artefact selectedArtefact)
        {
            if (selectedArtefact != null)
            {
                double newVersion = 1.0;
                foreach (Artefact a in artefactList)
                {
                    if (a.Id == selectedArtefact.Id && a.AgencyId == selectedArtefact.AgencyId)
                    {
                        try
                        {
                            double dv = System.Convert.ToDouble(selectedArtefact.Version);
                            dv += 0.1;
                            if (newVersion < dv)
                                newVersion = dv;
                        }
                        catch { }
                    }
                }

                //Change version, save temp, restore version, read saved and append it
                string oldVersion = selectedArtefact.Version;
                bool oldFinalState = selectedArtefact.IsFinal;
               
                selectedArtefact.Version = newVersion.ToString();
                if (selectedArtefact.Version.Contains(".") == false)
                    selectedArtefact.Version += ".0";

                selectedArtefact.IsFinal = false;

                string tempf = SaveTempArtefact(selectedArtefact);

                selectedArtefact.Version = oldVersion;
                selectedArtefact.IsFinal = oldFinalState;

                if (tempf.Length > 0)
                {
                    XmlDocument xDoc = new XmlDocument();
                    xDoc.Load(tempf);
                    //Create an XmlNamespaceManager for resolving namespaces.
                    SdmxNamespaceManager nsmgr = Xmlhelper.CreateSdmxNamespace(xDoc);

                    LoadFromDocument(xDoc, nsmgr);
                }
            }
        }
        #endregion

        #region XML serialize

        public virtual void WriteXml(XmlTextWriter xw, ISdmxArtefactRef selectedArtefact)
        {
            if (artefactList.Count > 0)
            {
                if (SdmxNamespaceManager.SdmxTarget == SdmxVersion.SDMX_21)
                    xw.WriteStartElement("structure", SdmxNamespaceManager.GetArtefactGroupTag(
                     SdmxNamespaceManager.SdmxTarget, artefactType), null);
                else
                {
                    // NO categorisation in 2.0
                    if (ArtefactType == SdmxArtefactType.Categorisations)
                        return;

                    xw.WriteStartElement(SdmxNamespaceManager.GetArtefactGroupTag(
                     SdmxNamespaceManager.SdmxTarget, artefactType));
                }

                string prefix = "structure";
                string tag = SdmxNamespaceManager.GetArtefactTag(
                    SdmxNamespaceManager.SdmxTarget, artefactType);

                foreach (Artefact a in artefactList)
                {   if (selectedArtefact == null || a == selectedArtefact)
                        a.WriteXml(xw, prefix, tag);
                }


                xw.WriteEndElement();
            }
        }
        #endregion

        #region Tree representation

        public virtual TreeNode FillNode(TreeNode parentNode, int startIndex)
        {
            TreeNode createdNode = null;
            // already attached to parent, Bind List of artefacts
            for (int i = startIndex; i < artefactList.Count; i++)
                createdNode = artefactList[i].FillNode(parentNode);
            return createdNode;
        }
        #endregion

        #region Artefact Creation

        /// <summary> 
        /// Method for creating an Artefact instance according to container type. 
        /// </summary>
        /// <param name="nd">Node </param>
        /// <param name="nsmgr">Namespace manager for XML interpretation</param>
        /// <returns>The new artefact instance or null if invalid call</returns>
        public virtual Artefact CreateArtefact(XmlNode nd, SdmxNamespaceManager nsmgr)
        {
            // Do it here for the moment
            Artefact a = null;
            switch (artefactType)
            {
                case SdmxArtefactType.OrganisationSchemes:
                        a = new OrganisationSchemesType(nd, nsmgr);
                        break;

                case SdmxArtefactType.CategorySchemes:
                        a = new CategorySchemeType(nd, nsmgr);
                        break;

                case SdmxArtefactType.Categorisations:
                        a = new CategorisationType(nd, nsmgr);
                        break;

                case SdmxArtefactType.CodeLists:
                        a = new CodeListType(nd, nsmgr);
                        break;

                case SdmxArtefactType.HierarchicalCodelists:
                        a = new HierarchicalCodelistType(nd, nsmgr);
                        break;

                case SdmxArtefactType.Concepts:
                        a = new ConceptSchemeType(nd, nsmgr);
                        break;

                case SdmxArtefactType.StructureSets:
                        a = new StructureSetType(nd, nsmgr);
                        break;

                case SdmxArtefactType.KeyFamilies:
                        a = new KeyFamilyType(nd, nsmgr);
                        break;

                case SdmxArtefactType.Dataflows:
                        a = new DataflowType(nd, nsmgr);
                        break;

                case SdmxArtefactType.Metadataflows:
                        a = new MetadataflowType(nd, nsmgr);
                        break;

                case SdmxArtefactType.MetadataStructureDefinitions:
                        a = new MetadataStructureDefinitionType(nd, nsmgr);
                        break;

                case SdmxArtefactType.Processes:
                        a = new ProcessType(nd, nsmgr);
                        break;

                case SdmxArtefactType.ReportingTaxonomies:
                        a = new ReportingTaxonomyType(nd, nsmgr);
                        break;

                case SdmxArtefactType.MetadataSet:
                        a = new MetadataSetType(nd, nsmgr);
                        break;

            }

            return a;
        }

        /// <summary> 
        /// Method for creating an Artefact instance according to container type. 
        /// </summary>
        /// <returns>The new artefact instance or null if invalid call</returns>
        public virtual Artefact CreateArtefact()
        {
            // Do it here for the moment
            Artefact a = null;
            switch (artefactType)
            {
                case SdmxArtefactType.OrganisationSchemes:
                    a = new OrganisationSchemesType();
                    break;

                case SdmxArtefactType.CodeLists:
                    a = new CodeListType();
                    break;

                case SdmxArtefactType.HierarchicalCodelists:
                    a = new HierarchicalCodelistType();
                    break;

                case SdmxArtefactType.Concepts:
                    a = new ConceptSchemeType();
                    break;

                case SdmxArtefactType.CategorySchemes:
                    a = new CategorySchemeType();
                    break;

                case SdmxArtefactType.Categorisations:
                    a = new CategorisationType();
                    break;

                case SdmxArtefactType.StructureSets:
                    a = new StructureSetType();
                    break;

                case SdmxArtefactType.KeyFamilies:
                    a = new KeyFamilyType();
                    break;

                case SdmxArtefactType.Dataflows:
                    a = new DataflowType();
                    break;

                case SdmxArtefactType.Metadataflows:
                    a = new MetadataflowType();
                    break;

                case SdmxArtefactType.MetadataStructureDefinitions:
                    a = new MetadataStructureDefinitionType();
                    break;

                case SdmxArtefactType.Processes:
                    a = new ProcessType();
                    break;

                case SdmxArtefactType.ReportingTaxonomies:
                    a = new ReportingTaxonomyType();
                    break;

                case SdmxArtefactType.MetadataSet:
                    a = new MetadataSetType();
                    break;
            }

            return a;
        }
        #endregion

        #region String representation
        public override string ToString()
        {
            string text = artefactType.ToString();
            if (text == "KeyFamilies")
                text = "Data Structure Definition"; // Sdmx recommendation of new naming
            else if (text == "Concepts")
                text = "ConceptSchemes"; // Sdmx 2 no concepts at root

            return text;
        }
        #endregion

        #region ISdmxNewChild Members

        public virtual TreeNode NewChild(TreeNode node)
        {
            TreeNode ret = node;
            Artefact a = CreateArtefact();
            if (a != null)
            {
                artefactList.Add(a);
                TreeNode t = a.FillNode(node);
                ret = t;
            }
            return ret;
        }

        public virtual void DelChild(object o)
        {
            Artefact a = o as Artefact;
            if (a != null && artefactList.Contains(a))
                artefactList.Remove(a);
        }
        #endregion
    }

    #region Artefact manager List

    /// <summary>Container of all managed artefacts </summary>
    public class ArtefactManagerDico : Dictionary<SdmxArtefactType, ArtefactManager>
    {
    }

    #endregion // Artefact manager List


    #region Class ArtefactList

    public class ArtefactList : List<Artefact>
    {
        public bool Exist(Artefact a)
        {
            foreach (Artefact art in this)
                if (art.Id == a.Id && art.Version == a.Version && art.AgencyId == a.AgencyId)
                    return true;
            return false;
        }

        /// <summary>Create table container object</summary>
        /// <returns>The dataTable object</returns>
        private DataTable CreateTableContainer()
        {
            //"Code", "Description"
            DataTable dataTable = new DataTable();
            dataTable.Columns.Add(new DataColumn("Code", typeof(string)));
            dataTable.Columns.Add(new DataColumn("Agency", typeof(string)));
            dataTable.Columns.Add(new DataColumn("Description", typeof(string)));

            return dataTable;
        }

        public DataTable GetTableRepresentation()
        {
            DataTable dataTable = CreateTableContainer();
            List<Artefact> arl = new List<Artefact>(this);
            //   arl.Sort();
            foreach (Artefact art in arl)
                dataTable.Rows.Add(new object[] { art.Id, art.Version + " - " + art.Name.ToString(), art.AgencyId });

            return dataTable;
        }
    }

    #endregion //Class ArtefactList

}
